package lelouet.datacenter.thermal.impacts;

import gridlib.api.Grid;
import gridlib.api.Machine;

import java.util.Collection;

import lelouet.datacenter.ArrayModel;
import lelouet.datacenter.thermal.PowerToHeat;

/**
 * Model for generating heats from servers consumptions on a linear way.<br />
 * The model is : each server has air entering it at a base heat, and each
 * working server increases its air temperature proportionally to its power
 * consumption.<br />
 * It can only work on a {@link Grid} that it knows the servers at contruction<br />
 */
public class LinearModel extends ArrayModel implements PowerToHeat {

	/**
	 * the table of impacts of servers on servers : impact[i][j] is the
	 * multiplier to apply on server[i] consumption
	 */
	protected double[][] impacts;

	/** the constant values, for each server */
	protected double[] constants;

	/**
	 * constructs a new {@link LinearModel} from another, using its map of
	 * servers, but allocating 0 for each {@link #impacts} and
	 * {@link #constants}
	 */
	public LinearModel(LinearModel other) {
		super(other);
		resetTables();
	}

	/**
	 * constructs a new {@link LinearModel} supposed to only map elements from a
	 * given {@link Collection} of {@link Machine}. All {@link #impacts} and
	 * {@link #constants} are 0
	 */
	public LinearModel(Collection<Machine> machines) {
		super(machines);
		resetTables();
	}

	/** constructs a LinearModel that only contains data of the impact map */
	public LinearModel(ImpactMap impacts) {
		super(impacts.getServerNames());
		resetTables();
		for (String fromName : getNames()) {
			for (String toName : getNames()) {
				setImpact(fromName, toName, impacts.getImpact(fromName, toName));
			}
		}
	}

	/**
	 * reset the tables with sizes according to {@link #coding} size and 0
	 * values everywhere
	 */
	private void resetTables() {
		int size = size();
		constants = new double[size];
		impacts = new double[size][size];
	}

	/**
	 * Load an {@link ImpactMap} in {@link #impacts}
	 * 
	 * @throws UnexistantServerException
	 *             if there are servers name in map that were not given at
	 *             construction
	 */
	public void load(ImpactMap map) throws UnexistantServerException {
		for (String impacter : map.getServerNames()) {
			Integer from = getIndex(impacter);
			if (from == null) {
				throw new UnexistantServerException(impacter);
			}
			for (String impacted : map.getServerNames()) {
				Integer to = getIndex(impacted);
				if (to == null) {
					throw new UnexistantServerException(impacted);
				}
				impacts[from][to] = map.getImpact(impacter, impacted);
			}
		}
	}

	/** set the impact between servers at specified index. */
	public void setImpact(int from, int to, double val) {
		impacts[from][to] = val;
	}

	public void setImpact(String from, String to, double val) {
		setImpact(getIndex(from), getIndex(to), val);
	}

	/** @return the impact registered that from has on to */
	public double getImpact(int from, int to) {
		return impacts[from][to];
	}

	/** @return the impact of server from to server to */
	public double getImpact(String from, String to) {
		return getImpact(getIndex(from), getIndex(to));
	}

	/** set the same constant value to all servers */
	public void setGlobalBase(double base) {
		for (int i = 0; i < constants.length; i++) {
			constants[i] = base;
		}
	}

	/** sets the constant value that applies on server at given index */
	public void setBase(int index, double value) {
		constants[index] = value;
	}

	/** sets the constant value that applies on server named */
	public void setBase(String name, double value) {
		setBase(getIndex(name), value);
	}

	/** @return the constant value that applies on server at given index */
	public double getBase(int index) {
		return constants[index];
	}

	/** @return the constant value that applies on server with given name */
	public double getBase(String name) {
		return getBase(getIndex(name));
	}

	public class UnexistantServerException extends Exception {

		private static final long serialVersionUID = 1L;

		public String serverName;

		public UnexistantServerException(String name) {
			serverName = name;
		}

		@Override
		public String toString() {
			return "UnexistantServerException[" + serverName + "]";
		}

	}

	/**
	 * for each server i, its maximum heat increase(in °C) for the servers given
	 * consumption vector
	 */
	public double[] maxHeatIncrease(double[] consumptions) {
		double[] ret = new double[size()];
		for (int impacterIndex = 0; impacterIndex < size(); impacterIndex++) {
			double consumption = consumptions[impacterIndex];
			for (int victimIndex = 0; victimIndex < size(); victimIndex++) {
				ret[victimIndex] += consumption
						* getImpact(impacterIndex, victimIndex);
			}
		}
		return ret;
	}

	@Override
	public double[] convert(double[] consumption) {
		double[] ret = new double[size()];
		for (int i = 0; i < size(); i++) {
			ret[i] += constants[i];
			for (int j = 0; j < size(); j++) {
				ret[j] += consumption[i] * getImpact(i, j);
			}
		}
		return ret;
	}
}
